﻿#if NETCOREAPP3_0
using Microsoft.Extensions.DependencyInjection;
using System.Text.Json;
#else
using Newtonsoft.Json;
#endif
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;

namespace System.Net.Http
{
    /// <summary>
    /// HttpClient 操作扩展类，内部保护了异常
    /// </summary>
    public static class HttpClientExtensions
    {
#if NETCOREAPP3_0
        /// <summary>
        /// 通过指定 uri 获取 json 数据方法
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="client"></param>
        /// <param name="requestUri"></param>
        /// <param name="callback"></param>
        /// <param name="token"></param>
        /// <param name="options"></param>
        /// <returns></returns>
        public static async Task<T> GetAsJsonAsync<T>(this HttpClient client, string requestUri = null, Action<Exception> callback = null, CancellationToken token = default, JsonSerializerOptions options = null) => await JsonAsync<T>(() => client.GetAsync(requestUri, token), callback, options);

        /// <summary>
        /// 通过指定 uri 获取 json 数据方法
        /// </summary>
        /// <typeparam name="TValue"></typeparam>
        /// <param name="client"></param>
        /// <param name="requestUri"></param>
        /// <param name="t"></param>
        /// <param name="token"></param>
        /// <param name="options"></param>
        /// <returns></returns>
        public static async Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, string requestUri, TValue t, CancellationToken token = default, JsonSerializerOptions options = null) => await client.SendAsync(HttpMethod.Post, requestUri, t, token, options);

        /// <summary>
        /// 通过指定 uri 获取 json 数据方法
        /// </summary>
        /// <typeparam name="TValue"></typeparam>
        /// <param name="client"></param>
        /// <param name="requestUri"></param>
        /// <param name="t"></param>
        /// <param name="token"></param>
        /// <param name="options"></param>
        /// <returns></returns>
        public static async Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient client, string requestUri, TValue t, CancellationToken token = default, JsonSerializerOptions options = null) => await client.SendAsync(HttpMethod.Put, requestUri, t, token, options);

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TValue"></typeparam>
        /// <param name="client"></param>
        /// <param name="requestUri"></param>
        /// <param name="t"></param>
        /// <param name="token"></param>
        /// <param name="options"></param>
        /// <returns></returns>
        public static async Task<HttpResponseMessage> DeleteAsJsonAsync<TValue>(this HttpClient client, string requestUri, TValue t, CancellationToken token = default, JsonSerializerOptions options = null) => await client.SendAsync(HttpMethod.Delete, requestUri, t, token, options);

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TValue"></typeparam>
        /// <param name="client"></param>
        /// <param name="method"></param>
        /// <param name="requestUri"></param>
        /// <param name="t"></param>
        /// <param name="token"></param>
        /// <param name="options"></param>
        /// <returns></returns>
        public static async Task<HttpResponseMessage> SendAsync<TValue>(this HttpClient client, HttpMethod method, string requestUri, TValue t, CancellationToken token = default, JsonSerializerOptions options = null)
        {
            if (options == null) options = new JsonSerializerOptions().AddDefaultConverters();
            var req = new HttpRequestMessage(method, requestUri);
            req.Content = new StringContent(JsonSerializer.Serialize(t, options));
            req.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");

            return await client.SendAsync(req, token);
        }
#else
        /// <summary>
        /// 通过指定 uri 获取 json 数据方法
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="client"></param>
        /// <param name="requestUri"></param>
        /// <param name="callback"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public static async Task<T> GetAsJsonAsync<T>(this HttpClient client, string requestUri = null, Action<Exception> callback = null, CancellationToken token = default) => await JsonAsync<T>(() => client.GetAsync(requestUri, token), callback);

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TValue"></typeparam>
        /// <param name="client"></param>
        /// <param name="requestUri"></param>
        /// <param name="t"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public static async Task<HttpResponseMessage> DeleteAsJsonAsync<TValue>(this HttpClient client, string requestUri, TValue t, CancellationToken token = default)
        {
            var req = new HttpRequestMessage(HttpMethod.Delete, requestUri);
            req.Content = new StringContent(JsonConvert.SerializeObject(t));
            req.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");

            return await client.SendAsync(req, token);
        }
#endif

#if NETCOREAPP3_0
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TValue"></typeparam>
        /// <typeparam name="TRet"></typeparam>
        /// <param name="client"></param>
        /// <param name="requestUri"></param>
        /// <param name="t"></param>
        /// <param name="callback"></param>
        /// <param name="token"></param>
        /// <param name="options"></param>
        /// <returns></returns>
        public static async Task<TRet> PostAsJsonAsync<TValue, TRet>(this HttpClient client, string requestUri, TValue t, Action<Exception> callback = null, CancellationToken token = default, JsonSerializerOptions options = null) => await JsonAsync<TRet>(() => client.PostAsJsonAsync(requestUri, t, token, options), callback, options);

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TValue"></typeparam>
        /// <typeparam name="TRet"></typeparam>
        /// <param name="client"></param>
        /// <param name="requestUri"></param>
        /// <param name="t"></param>
        /// <param name="callback"></param>
        /// <param name="token"></param>
        /// <param name="options"></param>
        /// <returns></returns>
        public static async Task<TRet> PutAsJsonAsync<TValue, TRet>(this HttpClient client, string requestUri, TValue t, Action<Exception> callback = null, CancellationToken token = default, JsonSerializerOptions options = null) => await JsonAsync<TRet>(() => client.PutAsJsonAsync(requestUri, t, token, options), callback, options);

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TValue"></typeparam>
        /// <typeparam name="TRet"></typeparam>
        /// <param name="client"></param>
        /// <param name="requestUri"></param>
        /// <param name="t"></param>
        /// <param name="callback"></param>
        /// <param name="token"></param>
        /// <param name="options"></param>
        /// <returns></returns>
        public static async Task<TRet> DeleteAsJsonAsync<TValue, TRet>(this HttpClient client, string requestUri, TValue t, Action<Exception> callback = null, CancellationToken token = default, JsonSerializerOptions options = null) => await JsonAsync<TRet>(() => client.DeleteAsJsonAsync(requestUri, t, token, options), callback, options);
#else
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TValue"></typeparam>
        /// <typeparam name="TRet"></typeparam>
        /// <param name="client"></param>
        /// <param name="requestUri"></param>
        /// <param name="t"></param>
        /// <param name="callback"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public static async Task<TRet> PostAsJsonAsync<TValue, TRet>(this HttpClient client, string requestUri, TValue t, Action<Exception> callback = null, CancellationToken token = default) => await JsonAsync<TRet>(() => client.PostAsJsonAsync(requestUri, t, token), callback);

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TValue"></typeparam>
        /// <typeparam name="TRet"></typeparam>
        /// <param name="client"></param>
        /// <param name="requestUri"></param>
        /// <param name="t"></param>
        /// <param name="callback"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public static async Task<TRet> PutAsJsonAsync<TValue, TRet>(this HttpClient client, string requestUri, TValue t, Action<Exception> callback = null, CancellationToken token = default) => await JsonAsync<TRet>(() => client.PutAsJsonAsync(requestUri, t, token), callback);

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TValue"></typeparam>
        /// <typeparam name="TRet"></typeparam>
        /// <param name="client"></param>
        /// <param name="requestUri"></param>
        /// <param name="t"></param>
        /// <param name="callback"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public static async Task<TRet> DeleteAsJsonAsync<TValue, TRet>(this HttpClient client, string requestUri, TValue t, Action<Exception> callback = null, CancellationToken token = default) => await JsonAsync<TRet>(() => client.DeleteAsJsonAsync(requestUri, t, token), callback);
#endif

#if NETCOREAPP3_0
        private static async Task<T> JsonAsync<T>(Func<Task<HttpResponseMessage>> requestCall, Action<Exception> callback = null, JsonSerializerOptions options = null)
#else
        private static async Task<T> JsonAsync<T>(Func<Task<HttpResponseMessage>> requestCall, Action<Exception> callback = null)
#endif
        {
            try
            {
                var response = await requestCall();
                if (response.IsSuccessStatusCode)
                {
                    var content = await response.Content.ReadAsStringAsync();
#if NETCOREAPP3_0
                    if (options == null) options = new JsonSerializerOptions().AddDefaultConverters();
                    return JsonSerializer.Deserialize<T>(content, options);
#else
                    return JsonConvert.DeserializeObject<T>(content);
#endif
                }
                else
                {
                    callback?.Invoke(new Exception($"{response.StatusCode}:{response.RequestMessage.RequestUri.AbsoluteUri}"));
                }
            }
            catch (Exception ex)
            {
                callback?.Invoke(ex);
            }
            return default;
        }
    }
}
